#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <string>
#include <vector>
#include <algorithm>

#include "UserMemalloc.h"
#include "GetFileList.h"
#include "FileInterfaceExt.h"
#include "crc.h"
using namespace FILESYS;
#include "SQLiteFS.h"
using namespace SQLITE_FS;

#define TEST_READ 0

#pragma warning(disable:4996)

static int gPendingWriteCount=0;
static int gPendingReadCount=0;

#define USE_COMPRESSION 1


#if USE_COMPRESSION

#include "fastlz.h"

struct CompressHeader
{
	CompressHeader(void)
	{
		mTag[0] = 'F';
		mTag[1] = 'S';
		mTag[2] = 'L';
		mTag[3] = 'Z';
		mInputLength = 0;
		mCRC = 0;
	}
	bool validate(void) const
	{
		bool ret = false;
		if ( mTag[0] == 'F' && mTag[1] == 'S' && mTag[2] == 'L' && mTag[3] == 'Z' )
		{
			ret = true;
		}
		return ret;
	}
	char	mTag[4];
	NxU32	mInputLength;
	NxU32	mCRC;
};
#endif

static const char * lastSlash(const char *fname)
{
	const char *ret = NULL;
	const char *scan = fname;
	while ( *scan )
	{
		if ( *scan == '/' || *scan == '\\' )
		{
			ret = scan;
		}
		scan++;
	}
	return ret;
}

static const char * getBaseName(const char *fname)
{
	const char *ret = fname;
	const char *slash = lastSlash(fname);
	if ( slash )
	{
		ret = slash+1;
	}
	return ret;
}

class MyWriteFile : public IWriteFile
{
public:
	MyWriteFile( const char *path )
	{
		mFullPathName = path;
		mFqn = getBaseName(path);
		mInfo.fqn = mFqn;
		mInfo.fqnSize = strlen( mInfo.fqn );
		mMem = 0;
		gPendingWriteCount++;
	}

	virtual ~MyWriteFile()
	{
		release();
		gPendingWriteCount--;
	}

	void release()
	{
		mInfo = SQLiteFS_file();
		if ( mMem )
			MEMALLOC_FREE( mMem );
	}

	virtual void fetchFQN( SQLiteFS_file& info )
	{
		printf("Begin file write %s\r\n", mFqn);
		info.fqn = mFqn;
		info.fqnSize = strlen( mFqn );
	}
#if USE_COMPRESSION
	void * compress(const void *mem,NxU32 len,NxU32 &outlen)
	{
		NxU32 inputLength = len;
		char scratch[16];
		if ( len < 16 )
		{
			memset(scratch,0,16);
			memcpy(scratch,mem,len);
			mem = scratch;
			len = 16;
		}
		NxU32 outEstimate = len+len/2;
		if ( outEstimate < 128 ) outEstimate = 128;
		CompressHeader *compressedMemory = (CompressHeader *)MEMALLOC_MALLOC(outEstimate);
		new ( compressedMemory ) CompressHeader;
		CompressHeader *nextHeader = compressedMemory+1;
		outlen = fastlz_compress(mem,len,nextHeader);
		compressedMemory->mInputLength = inputLength;
		compressedMemory->mCRC = computeCRC((const NxU8 *)nextHeader,outlen);
		outlen+=sizeof(CompressHeader);
		printf("Compressed input from %d bytes down to %d.\r\n", inputLength, outlen );
		return compressedMemory;
	}
#endif

	// WARNING: Must be threadsafe!  Executes in a different thread!
	virtual bool fetchFileData( SQLiteFS_file& info )
	{
		bool ret = false;
		if ( !mInfo.fileMem )
		{
			if ( fs_fileExists( mFullPathName ) )
			{
				if ( fs_fileTimestamp( mFullPathName, mInfo.timestamp ) )
				{
					assert( !mMem );
					mMem = fs_readEntireFile( mFullPathName, mInfo.fileSize );
					if ( mMem )
					{
#if USE_COMPRESSION
						mInfo.crc32 = computeCRC( (const NxU8 *)mMem, mInfo.fileSize );
						NxU32 compressLen;
						void *compressMem = compress(mMem,mInfo.fileSize,compressLen);
						MEMALLOC_FREE(mMem);
						mMem = compressMem;
						mInfo.fileSize = compressLen;
#endif
						mInfo.fileMem = mMem;
						info = mInfo;
						ret = true;
					}
				}
			}
		}
		return ret;
	}

	void onFilePut( const SQLiteFS_file& file, bool succeeded )
	{
		// bypass unreferenced params warning.
		(file);
		if ( succeeded )
			printf("File (%s) WRITE (%d) bytes: SUCCESS\r\n", file.fqn, file.fileSize);
		else
			printf("File (%s) WRITE (%d) bytes: FAIL\r\n", file.fqn, file.fileSize);
		delete this;
	}

	SQLiteFS_file		mInfo;
	const char				*mFqn;
	const char				*mFullPathName;
	void				*mMem;
};

class MyReadFile : public IReadFile
{
public:
	MyReadFile( const char *path )
	{
		mFqn = path;
		mMem = NULL;
		mLen = 0;
		gPendingReadCount++;
	}

	virtual ~MyReadFile()
	{
		MEMALLOC_FREE(mMem);
		gPendingReadCount--;
	}

	virtual void fetchFQN( SQLiteFS_file& info )
	{
		info.fqn = mFqn;
		info.fqnSize = strlen( mFqn );
	}

	virtual void onFileRead( const SQLiteFS_file &file, bool succeeded )
	{
		// bypass unreferenced params warning.
		if ( succeeded )
		{
			assert( file.fileMem );
#if USE_COMPRESSION
			CompressHeader *ch = (CompressHeader *)file.fileMem;
			assert( ch->validate() );
			const NxU8 *compressData = (const NxU8 *)(ch+1);
			NxU32 compressLen = file.fileSize-sizeof(CompressHeader);
			NxU32 crc = computeCRC(compressData,compressLen);
			assert( crc == ch->mCRC );
			mMem = MEMALLOC_MALLOC(ch->mInputLength);
			mLen = ch->mInputLength;
			
			int ok = fastlz_decompress(compressData,compressLen,mMem,mLen);
			assert(ok!=0);
#else
			mMem = MEMALLOC_MALLOC( file.fileSize );
			memcpy(mMem,file.fileMem,file.fileSize);
			mLen = file.fileSize;
#endif

			printf("Successfully read file '%s'\r\n", file.fqn );
		}
		else
		{
			printf("FAILED to read file '%s'\r\n", file.fqn );
		}
		delete this;
	}

	const char				*mFqn;
	void				*mMem;
	NxU32				mLen;
};




void main(int argc,const char **argv)
{
	if ( argc < 2 )
	{
		printf("Usage: SqliteFS <subdirname> <optiona_wildcard>\r\n");
		printf("\r\n");
		printf("This tool will add the contents of a sub-directory to an Sqlite database.\r\n");
	}
	else
	{
		const char *spec = "*.*";
		if ( argc == 3 )
		{
			spec = argv[2];
		}
		printf("Getting file list from directory: %s using spec %s.\r\n", argv[1], spec );
		GET_FILE_LIST::FileListVector flist;
		unsigned int fcount = GET_FILE_LIST::getFileList(argv[1],spec,true,flist);
		if ( fcount != 0 )
		{
			std::sort( flist.begin(), flist.end() );
#if TEST_READ

			printf("Found %d files; now adding them to the Sqlite Database.\r\n", fcount);
			char dataBaseName[512];
			sprintf_s(dataBaseName,512,"./%s.db",argv[1]);
			SQLiteFS *fileSystem = createSQLiteFS(dataBaseName,true);
			if ( fileSystem )
			{
				printf("Sending %d file read requests.\r\n", fcount);
				for (unsigned int i=0; i<fcount; i++)
				{
					const char *fname = flist[i].c_str();
					char scratch[512];
					sprintf_s(scratch,512,"%s",fname);
					strlwr(scratch);
					fileSystem->getFile( new MyReadFile(getBaseName(scratch)), false );
				}
				printf("Waiting for the file reads to complete.\r\n");

				while ( gPendingReadCount != 0 )
				{
					fileSystem->pump();
				}
				printf("Successfully read %d files.\r\n", fcount );
				releaseSQLiteFS(fileSystem);
			}
			else
			{
				printf("Failed to create and/or open sqlite data base '%s'\r\n", dataBaseName );
			}


#else
			printf("Found %d files; now adding them to the Sqlite Database.\r\n", fcount);
			char dataBaseName[512];
			sprintf_s(dataBaseName,512,"./%s.db",argv[1]);
			SQLiteFS *fileSystem = createSQLiteFS(dataBaseName,true,true);
			if ( fileSystem )
			{
				printf("Sending %d file write requests.\r\n", fcount);
				for (unsigned int i=0; i<fcount; i++)
				{
					const char *fname = flist[i].c_str();
					char scratch[512];
					sprintf_s(scratch,512,"%s",fname);
					strlwr(scratch);
					fileSystem->putFile( new MyWriteFile(scratch), false );
				}
				printf("Waiting for the file write's to complete.\r\n");

				while ( gPendingWriteCount != 0 )
				{
					fileSystem->pump();
				}
				printf("Successfully wrote %d files.\r\n", fcount );
				releaseSQLiteFS(fileSystem);
			}
			else
			{
				printf("Failed to create and/or open sqlite data base '%s'\r\n", dataBaseName );
			}
#endif
		}
		else
		{
			printf("No files found.\r\n");
		}
	}
}
